#pragma once
#include "DXUT.h"
#include "DXUTcamera.h"
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <string>
#include "StaticMesh.h"
#include "SkinnedMesh.h"
#include "MorphingMesh.h"
#include <vector>
using std::string;
using std::vector;

#include "..\\MeshRender.h"
#include "ColladaLoader.h"

template <class CVertType, class CTargetVertType>
class ColladaRender: public CMeshRender
{
private:
	//Convert matrix
	D3DXMATRIX collada2DirectX(D3DXMATRIX input)
	{
		D3DXMATRIX out;

		out.m[0][0] = input.m[0][0];
		out.m[0][1] = input.m[1][0];
		out.m[0][2] = input.m[2][0];
		out.m[0][3] = input.m[3][0];

		out.m[1][0] = input.m[0][1];
		out.m[1][1] = input.m[1][1];
		out.m[1][2] = input.m[2][1];
		out.m[1][3] = input.m[3][1];

		out.m[2][0] = input.m[0][2];
		out.m[2][1] = input.m[1][2];
		out.m[2][2] = input.m[2][2];
		out.m[2][3] = input.m[3][2];

		out.m[3][0] = input.m[0][3];
		out.m[3][1] = input.m[1][3];
		out.m[3][2] = input.m[2][3];
		out.m[3][3] = input.m[3][3];

		return out;
	}
public:
	COLLADALoader<CVertType,CTargetVertType>* m_models;
	//Constructor
	ColladaRender():CMeshRender()
	{
		m_models = NULL;
		m_iVType = CVertType::VType;
	}

	//Destructor
	virtual ~ColladaRender()
	{
		SAFE_DELETE(m_models);
	}

	virtual bool LoadModel(const char* pFileName, ID3D11Device* pd3dDevice)
	{
		SAFE_DELETE(m_models);
		m_models = new COLLADALoader<CVertType,CTargetVertType>();
		string sFileName;
		sFileName.assign(pFileName);
		if(m_models->Load(sFileName))
			m_bModelReady = true;
		return m_bModelReady;
	}
	//Convert each mesh of all types to be DirectX friendly
	void makeDirectXFriendly()
	{
		//Create negative z scale matrix
		D3DXMATRIX Scale; D3DXMatrixScaling(&Scale, 1, 1, -1);
		//Convert static meshes
		for(unsigned int i = 0; i < m_models->StaticMeshes.size(); i++) {
			//Multiply World by Scale
			m_models->StaticMeshes[i]->SetWorldMatrix( collada2DirectX(m_models->StaticMeshes[i]->GetWorldMatrix()) * Scale );
			//Change Index buffer to render counter clockwise
			for(unsigned int z = 0; z < m_models->StaticMeshes[i]->Indices.size(); (z += 3)) {
				unsigned int v0 = m_models->StaticMeshes[i]->Indices[z];
				unsigned int v1 = m_models->StaticMeshes[i]->Indices[z+1];
				unsigned int v2 = m_models->StaticMeshes[i]->Indices[z+2];

				m_models->StaticMeshes[i]->Indices[z] = v0;
				m_models->StaticMeshes[i]->Indices[z+1] = v2;
				m_models->StaticMeshes[i]->Indices[z+2] = v1;
			}
		}

		//Convert skinned meshes
		for(unsigned int i = 0; i < m_models->SkinnedMeshes.size(); i++)
		{
			//Set Root Transform to Scale
			m_models->SkinnedMeshes[i]->RootTransform = collada2DirectX(m_models->SkinnedMeshes[i]->RootTransform) * Scale;

			//Convert each Animation matrix
			for(unsigned int z = 0; z < m_models->SkinnedMeshes[i]->Animations.size(); z++) 
				m_models->SkinnedMeshes[i]->Animations[z].Matrix = collada2DirectX(m_models->SkinnedMeshes[i]->Animations[z].Matrix);

			//Convert the bind and inverse bind matrices
			for(unsigned int z = 0; z < m_models->SkinnedMeshes[i]->Joints.size(); z++)
			{
				m_models->SkinnedMeshes[i]->Joints[z].bind_matrix = collada2DirectX(m_models->SkinnedMeshes[i]->Joints[z].bind_matrix);
				m_models->SkinnedMeshes[i]->Joints[z].inv_bind_matrix = collada2DirectX(m_models->SkinnedMeshes[i]->Joints[z].inv_bind_matrix);
			}

			//Convert BindShape
			m_models->SkinnedMeshes[i]->BindShape = collada2DirectX(m_models->SkinnedMeshes[i]->BindShape);

			//Change Index buffer to render counter clockwise
			for(unsigned int z = 0; z < m_models->SkinnedMeshes[i]->Indices.size(); (z += 3))
			{
				unsigned int v0 = m_models->SkinnedMeshes[i]->Indices[z];
				unsigned int v1 = m_models->SkinnedMeshes[i]->Indices[z+1];
				unsigned int v2 = m_models->SkinnedMeshes[i]->Indices[z+2];

				m_models->SkinnedMeshes[i]->Indices[z] = v0;
				m_models->SkinnedMeshes[i]->Indices[z+1] = v2;
				m_models->SkinnedMeshes[i]->Indices[z+2] = v1;
			}
		}

		//Convert morphing meshes
		for(unsigned int i = 0; i < m_models->MorphingMeshes.size(); i++)
		{
			//Multiply World of Base mesh by Scale
			m_models->MorphingMeshes[i]->Base->SetWorldMatrix( collada2DirectX(m_models->MorphingMeshes[i]->Base->GetWorldMatrix()) * Scale );

			//Multiply World of each Target mesh by Scale
			for(unsigned int z = 0; z < m_models->MorphingMeshes[i]->Targets.size(); z++)
			{
				m_models->MorphingMeshes[i]->Targets[z]->SetWorldMatrix(collada2DirectX(m_models->MorphingMeshes[i]->Targets[z]->GetWorldMatrix()) * Scale);

				//Change Index buffer to render counter clockwise for Targets
				for(unsigned int x = 0; x < m_models->MorphingMeshes[i]->Targets[z]->Indices.size(); (x += 3))
				{
					unsigned int v0 = m_models->MorphingMeshes[i]->Targets[z]->Indices[x];
					unsigned int v1 = m_models->MorphingMeshes[i]->Targets[z]->Indices[x+1];
					unsigned int v2 = m_models->MorphingMeshes[i]->Targets[z]->Indices[x+2];

					m_models->MorphingMeshes[i]->Targets[z]->Indices[x] = v0;
					m_models->MorphingMeshes[i]->Targets[z]->Indices[x+1] = v2;
					m_models->MorphingMeshes[i]->Targets[z]->Indices[x+2] = v1;
				}
			}

			//Change Index buffer to render counter clockwise for Base
			for(unsigned int z = 0; z < m_models->MorphingMeshes[i]->Base->Indices.size(); (z += 3))
			{
				unsigned int v0 = m_models->MorphingMeshes[i]->Base->Indices[z];
				unsigned int v1 = m_models->MorphingMeshes[i]->Base->Indices[z+1];
				unsigned int v2 = m_models->MorphingMeshes[i]->Base->Indices[z+2];

				m_models->MorphingMeshes[i]->Base->Indices[z] = v0;
				m_models->MorphingMeshes[i]->Base->Indices[z+1] = v2;
				m_models->MorphingMeshes[i]->Base->Indices[z+2] = v1;
			}
		}
	}

	//onDeviceLost
	virtual void OnD3D11DestroyDevice( void* pUserContext )
	{
		//Handle static meshes
		for(unsigned int i = 0; i < m_models->StaticMeshes.size(); i++) 
			m_models->StaticMeshes[i]->OnD3D11DestroyDevice(pUserContext);

		//Handle skinned meshes
		for(unsigned int i = 0; i < m_models->SkinnedMeshes.size(); i++) 
			m_models->SkinnedMeshes[i]->OnD3D11DestroyDevice(pUserContext);

		//Handle morphing meshes
		for(unsigned int i = 0; i < m_models->MorphingMeshes.size(); i++) 
			m_models->MorphingMeshes[i]->OnD3D11DestroyDevice(pUserContext);
		
	}

	//onDeviceReset
	virtual HRESULT OnD3D11CreateDevice( ID3D11Device* pd3dDevice, ID3DX11EffectTechnique* pTech, const DXGI_SURFACE_DESC* pBackBufferSurfaceDesc, CDirectoryUtil* pDirUtil, void* pUserContext)
	{
		HRESULT hr = S_OK;
		if(!m_models)
		{
			printf("No model is loaded yet!!!\n");			
#ifdef _DEBUG
				OUTPUTERRORINFO;
#endif
			
			return E_FAIL;
		}
		//Handle static meshes
		for(unsigned int i = 0; i < m_models->StaticMeshes.size(); i++) 
			hr = m_models->StaticMeshes[i]->OnD3D11CreateDevice(pd3dDevice, pTech, pBackBufferSurfaceDesc,pDirUtil,pUserContext);

		//Handle skinned meshes
		for(unsigned int i = 0; i < m_models->SkinnedMeshes.size(); i++) 
			hr = m_models->SkinnedMeshes[i]->OnD3D11CreateDevice(pd3dDevice, pTech, pBackBufferSurfaceDesc,pDirUtil,pUserContext);

		//Handle morphing meshes
		for(unsigned int i = 0; i < m_models->MorphingMeshes.size(); i++) 
			hr = m_models->MorphingMeshes[i]->OnD3D11CreateDevice(pd3dDevice, pTech, pBackBufferSurfaceDesc,pDirUtil,pUserContext);
		
		//prepare for directx rendering
		makeDirectXFriendly();
		
		return hr;
	}
	//Update
	void Update(float dt)
	{
		//Update SkinnedMeshes
		for(unsigned int i = 0; i < m_models->SkinnedMeshes.size(); i++) 
			m_models->SkinnedMeshes[i]->Update(dt);

		//Update MorphingMeshes
		for(unsigned int i = 0; i < m_models->MorphingMeshes.size(); i++) 
			m_models->MorphingMeshes[i]->Update(dt);
	}

	//Draw
	virtual void Render(ID3D11Device* pd3dDevice, ID3D11DeviceContext* pd3dImmediateContext, ID3DX11Effect* pEffect, ID3DX11EffectTechnique* pRenderTech, 
						   enum RENDERMESHTECH iMeshType, const D3DXMATRIX& mWorldCamera, double fTime, float fElapsedTime, void* pUserContext)
	{
		switch (iMeshType)
		{
		case RENDERSTATICMESH:
			{	//Draw each Static Mesh
				for(unsigned int i = 0; i < m_models->StaticMeshes.size(); i++)
					m_models->StaticMeshes[i]->Render(pd3dDevice,pd3dImmediateContext, pEffect, pRenderTech, iMeshType, mWorldCamera, fTime,fElapsedTime,pUserContext);
			}
			break;
		case RENDERSKINMESH:
			{  //Draw each
				for(unsigned int i = 0; i < m_models->SkinnedMeshes.size(); i++)
					m_models->SkinnedMeshes[i]->Render(pd3dDevice,pd3dImmediateContext, pEffect, pRenderTech,iMeshType,mWorldCamera, fTime,fElapsedTime,pUserContext);
			}
			break;
		case RENDERMORPHMESH:
			{
				ID3DX11EffectTechnique* pStaticTech = pEffect->GetTechniqueByName("RenderStaticMesh");
				if(!pStaticTech) {
#ifdef _DEBUG
					OUTPUTERRORINFO;
#endif
						return;
				}
				//Draw each Morph Mesh's Morph Targets
				for(unsigned int i = 0; i < m_models->MorphingMeshes.size(); i++)
					m_models->MorphingMeshes[i]->RenderPeripheral(pd3dDevice,pd3dImmediateContext,pEffect, pStaticTech,RENDERMESHTECH::RENDERSTATICMESH, mWorldCamera, fTime, 
					 fElapsedTime,pUserContext);
				//Draw each
				for(unsigned int i = 0; i < m_models->MorphingMeshes.size(); i++)
					m_models->MorphingMeshes[i]->Render(pd3dDevice, pd3dImmediateContext, pEffect, pRenderTech, iMeshType, mWorldCamera, fTime, fElapsedTime,pUserContext);				
			}
			break;
		case RENDERMIXEDMESHES:
			RenderAllMeshes(pd3dDevice,pd3dImmediateContext,pEffect, mWorldCamera, fTime,fElapsedTime,pUserContext);
			break;
		}
	}
	virtual void RenderAllMeshes( ID3D11Device* pd3dDevice, ID3D11DeviceContext* pd3dImmediateContext, ID3DX11Effect* pEffect,const D3DXMATRIX& mWorldCamera,
		                          double fTime, float fElapsedTime, void* pUserContext )
	{
		if(!m_models) {
			printf("No model is loaded yet!!!\n");
#ifdef _DEBUG
			OUTPUTERRORINFO;
#endif
				return;
		}
		
#pragma region //Static meshes
		ID3DX11EffectTechnique* pStaticTech = pEffect->GetTechniqueByName("RenderStaticMesh");
		if(!pStaticTech) {
			OUTPUTERRORINFO
			return;
		}
		//Draw each Static Mesh
		for(unsigned int i = 0; i < m_models->StaticMeshes.size(); i++)
			m_models->StaticMeshes[i]->Render(pd3dDevice,pd3dImmediateContext, pEffect, pStaticTech, RENDERMESHTECH::RENDERSTATICMESH, mWorldCamera, fTime,fElapsedTime,pUserContext);
		
		//Draw each Morph Mesh's Morph Targets
		for(unsigned int i = 0; i < m_models->MorphingMeshes.size(); i++)
			m_models->MorphingMeshes[i]->RenderPeripheral(pd3dDevice,pd3dImmediateContext, pEffect,pStaticTech,RENDERMESHTECH::RENDERSTATICMESH, mWorldCamera, fTime,fElapsedTime,pUserContext);

#pragma endregion

#pragma region //Skinned meshes
		//Set effect globals
		ID3DX11EffectTechnique* pSkinTech = pEffect->GetTechniqueByName("RenderSkinMesh");
		if(pSkinTech) {
		//Draw each
		for(unsigned int i = 0; i < m_models->SkinnedMeshes.size(); i++)
			m_models->SkinnedMeshes[i]->Render(pd3dDevice,pd3dImmediateContext,pEffect,pSkinTech,RENDERMESHTECH::RENDERSKINMESH,mWorldCamera,fTime,fElapsedTime,pUserContext);		
		}
#pragma endregion

#pragma region //Morph meshes
		ID3DX11EffectTechnique* pMorphTech = pEffect->GetTechniqueByName("RenderMorphMesh");
		if(!pMorphTech) {
	    //Set Vertex Declaration!!!!
        //Morph animated mesh has more than one streams for rendering, and it will be fixed afterwards!!!
		//Draw each
		for(unsigned int i = 0; i < m_models->MorphingMeshes.size(); i++)
			m_models->MorphingMeshes[i]->Render(pd3dDevice,pd3dImmediateContext, pEffect, pMorphTech,RENDERMESHTECH::RENDERMORPHMESH, mWorldCamera, fTime,fElapsedTime,pUserContext);
	}
#pragma endregion
	}

	virtual HRESULT OnD3D11ResizedSwapChain( ID3D11Device* pd3dDevice, IDXGISwapChain* pSwapChain,
		const DXGI_SURFACE_DESC* pBackBufferSurfaceDesc, CDirectoryUtil* pDirUtil, void* pUserContext )
	{
		return S_OK;
	}
	
	virtual void OnD3D11ReleasingSwapChain( void* pUserContext )
	{
	}

	virtual void InitEffectsVariables(){}
	virtual void ReloadEffectsDebug(ID3D11Device* pd3dDevice, CDirectoryUtil* pDirUtil){};

};